package Network;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.Timer;
import taskmanager.ITaskManager;
import taskmanager.JTask;

/**
 * Реализация сервисов предоставляемых ведомым сервером, серверу арбитру
 * @author Volk = 1001
 */
public class SlaveServerController implements ISlaveServer {
  //---------------------Констатнты класса-------------------------------------------------------
  /**
    * Иия удалённой заглушки предоставляющие сервисы подчинённого сервера
  */
  private final String STUBSLAVENAME = "SlaveServerController";
  /**
   * Имя удалённой заглушки предоставляющей сервисы сервера арбитра
   */
  private final String STUBARBITRATORNAME = "ArbitrationController";

  /**
   * Имя файла для сохранения параметров сервера
   */
  private final String SETTINGS_PATH="./serverslave_address.ini";
  //---------------------Аттрибуты класса-------------------------------------------------------
  private boolean isCanBeSlave = true;  //Режим, при котором сервер может быть подчинённым
  private IArbitrationController arbitratorServer; //Ссылка на удалённый объект арбитра
  private String ArbitratorIP; //IP-адресс арбитра
  private int ArbitratorPort; //Порт арбитра
  private PingService serviceArbitratorPing = null; //Сервис для организации пинга арбитра
  private ITaskManager taskManager; //планировщик
  //Список обработчиков событий - Установка режима сервера в режим "Ведомый"
  private LinkedList<ActionListener> slaveActionList = new LinkedList<ActionListener>();
  //Список обработчиков событий - Получена команда отключения данного сервера от сети 
  private LinkedList<ActionListener> removeActionList = new LinkedList<ActionListener>();
  //Список обработчиков событий - Потеря связи с арбитром
  private LinkedList<ActionListener> failArbitratorActionList = new LinkedList<ActionListener>();
  //Список обработчиков событий - Восстановление связи с арбитром
  private LinkedList<ActionListener> reconnectArbitratorActionList = new LinkedList<ActionListener>();
  //Список обработчиков событий - Ведомый сервер стал членом сети
  private LinkedList<ActionListener> firstConnectActionList = new LinkedList<ActionListener>();

  //Число подсоединений сервера арбитра к серверу
  private int countInNet = 0;
  //---------------------------------------------------------------------------------------------
  //--------------------Сервисные методы---------------------------------------------------------
  /**
   * Инициализировать средства оповещения о потери связи с арбитром
   * @param ps - сервис по обеспечению пингования сервера арбитра
   */
  private void initPingListener(){
     //Обработчик потери соединения с арбитром
     if(serviceArbitratorPing != null) serviceArbitratorPing.stopPing();
     serviceArbitratorPing = new PingService(arbitratorServer, new NetworkAddress(ArbitratorIP,ArbitratorPort));
     serviceArbitratorPing.startPing();
     serviceArbitratorPing.addFailListener(new ActionListener(){
      public void actionPerformed(ActionEvent e) {
        activateActionListener(failArbitratorActionList);
        serviceArbitratorPing.stopPing();
        serviceArbitratorPing = null;
      }     
    });
  }

  /**
   * Активизировать список некоторых событий
   * @param actionlist список обработчиков событий
   */
  private void activateActionListener(LinkedList<ActionListener> actionlist){
    Iterator<ActionListener> iterator = actionlist.iterator();
    while(iterator.hasNext()){
     ActionListener listener = iterator.next();
     listener.actionPerformed(null);
    }
  }

  /**
   * Преобразровать URL адрес в RMI адрес
   * @param inetAddress - адрес хоста
   * @param port - адрес порта
   * @param stubName - имя заглушки
   */
  private String transformToRMIURL(String inetAddress, int port, String stubName){
     inetAddress.trim();
     if(inetAddress.contains("/" + stubName)) inetAddress = inetAddress.substring(0, inetAddress.length() - stubName.length() - "/".length());
     inetAddress+= ":" + port + "/" + stubName;

     if( (!inetAddress.contains("http") && (!inetAddress.contains("rmi")) )) inetAddress = "rmi:" + "/" + "/" + inetAddress;
     else inetAddress =  inetAddress.replace("http", "rmi");
     return inetAddress;
  }

  //---------------------------------------------------------------------------------------------
  
  /**
   * Конструктор 
   * @throws java.io.IOException в случае если произошла ошибка при инициализации файла со свойствами
   */
  public SlaveServerController(ITaskManager taskManager) throws IOException{
    this.taskManager=taskManager;
    try{
        load();
    }catch(IOException ioe){
        save();
    }
  }

  /**
   * Получить удалённый объект арбитра
   * @return интерфейс удалённого объекта арбитра, null - в случае если система не имеет арбитра
   */
  public IArbitrationController getArbitratorController(){
   if(s2sInnerLogic.mode == SLAVE_MODE) return arbitratorServer;
   else return null;
  }
   

  /**
   * Установить может ли сервер выступать в роли подчинённого
   * @param IsCanBeSlave
  */
  public void setCanBeSlave(boolean IsCanBeSlave){
    isCanBeSlave = IsCanBeSlave;
  }

  /**
   * Получить информацию о состоянии арбитра
   * @return информация о сервере арбитре
   */
  public NetworkAddress getArbitratorAddress(){
    NetworkAddress address = new NetworkAddress(ArbitratorIP,ArbitratorPort);
    //Получить состояние сервера
    if(!IsStateArbitrator()) address.SetDisable();
    return address;
  }

  /**
   * Получить состояние арбитра (доступен или нет)
   * @return true - арбитр досткпен, false - арбитр недоступен
   */
  public boolean IsStateArbitrator(){
   try{
    if(arbitratorServer == null) return false;
    arbitratorServer.ping();
   }catch(RemoteException re){return false;}
   return true;
  }
  /**
   * Проверяет запрос об сетевой активности сервера
   * @throws java.rmi.RemoteException - сервер не доступен
   */
  public void ping() throws RemoteException {}

  /**
   * Запрос о том, может ли запрашиваемый сервер стать подчинённым
   * @param IP - IP-адресс сервера инициировавшего запрос
   * @param port - номер порта сервера инициировавшего запро
   * @return идентитикатор сервера, -1 подключние невозможно
   * @throws java.rmi.RemoteException - разрыв соединения
   * @throws MalformedURLException - неверный IP - адресс обратная связь не возможно
   * @throws  NotBoundException - отсутсвует заглушка арбитра на инициирующей стороне - связь не возможна
  */
  public long connect(String IP,int port) throws RemoteException, MalformedURLException, NotBoundException {
   //Проверка совместимости операции с текущим режимом работы
   switch(s2sInnerLogic.mode){
       //Если сервер работает в автономном режиме, то можем беспрепятственно соединить их в сеть
       case AUTONOMIC_MODE:
       break; 
       //Если сервер является арбитром, то он не может стать подчинённым
       case ARBITRATOR_MODE: 
       return -1;
       //Если сервер является подчинённым, то необходимо проверить у этого ли арбитра
       case SLAVE_MODE: 
       break;
   }

   String inetAddress = transformToRMIURL(IP, port, STUBARBITRATORNAME); //Преобразовать входной адрес в URI по формату RMI
   //---------------------Установить диспетчер защиты------------------------------------------------------------
   NetworkAddress address = new NetworkAddress(IP,port);
   //Устанавливаем диспетчер защиты RMI
   System.setProperty("java.security.policy", "server.policy");
   RMISecurityManager manager = new RMISecurityManager();
   System.setSecurityManager(manager);

   //--------------------------------Получение класса заглушки, сохранение его в таблицу--------------------------
   arbitratorServer = (IArbitrationController) Naming.lookup(inetAddress);
   //Если уникальный идентификатор ещё не выдан, то выдаём его
   try{
   if(s2sInnerLogic.GUIDServer==0)
   //Получить уникальный идентификатор у своего сервера арбитра
    s2sInnerLogic.GUIDServer = arbitratorServer.generateServerID();
   }catch(IOException ioe){JOptionPane.showMessageDialog(null, "Ошибка сохранения параметров при регистрации ведомого сервера");}

   //Проверяем работает ли арбирт в автономном режиме:
   if(s2sInnerLogic.mode == ISlaveServer.AUTONOMIC_MODE){
   ArbitratorIP = IP; 
   ArbitratorPort = port;
   System.out.println("Сервер стал подчинённым, координаты арбитра: IP " + IP + " порт " + port);
   s2sInnerLogic.mode = ISlaveServer.SLAVE_MODE; //Устанавливаем режим работы сервера как подчинённый 
   //Активировать обработчики события - сервер стал подчинённым
   activateActionListener(slaveActionList);
   }
   initPingListener(); //Инициализируем обработчик для пинга клиента
   //Проверяем первый ли это коннект или арбитр падал
   if(countInNet == 0){
    //Первый коннект
    activateActionListener(firstConnectActionList);
   }else{
    //Арбитр падал
    activateActionListener(reconnectArbitratorActionList);
   }

   //Cохранение в файл
   try{
   save();
   }catch(IOException ioe){
    throw new RemoteException();
   }
   countInNet++; //Инкрементим количество подключений к сети
   return s2sInnerLogic.GUIDServer;
  }

   /**
    * Проверить находился ли сервер в сети в момент запуска
    * @return 0 - сервер не был подключен, иначе количество соединений перед бесперебойной работой
    * @throws java.rmi.RemoteException разрыв соединения с сервером
   */
   public int isInNet() throws RemoteException{
    return countInNet;
   }

   /**
    * Получить режим работы удалённого сервера
    * @throws RemoteException - разрыв соединения
    * @return режим работы удалённого сервера
    */
   public int getMode() throws RemoteException{
    return s2sInnerLogic.mode;
   }

   /**
    * Сохранить данные о текущем состоянии сервера как ведомого
    * @throws java.io.IOException ошибка при записи в поток
    */
   public void save() throws IOException{
    //создать поток

    ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(SETTINGS_PATH));
    try{
    int flag = isCanBeSlave==true?1:0;
    stream.writeInt(flag);
    if(s2sInnerLogic.mode  == SLAVE_MODE){
     stream.writeUTF(ArbitratorIP);
     stream.writeInt(ArbitratorPort);
    }
    }finally{stream.close();}
   }


   /**
    * Загрузить данные о текущем состоянии сервера как ведомого
    * @throws java.io.IOException ошибка при чтении из потока
    */
   public void load() throws IOException{
    //создать поток
    ObjectInputStream stream = new ObjectInputStream(new FileInputStream(SETTINGS_PATH));
    try{
    //Временное сохранние данных для подтверждения формата--------------
    int tmpCanBeSlave = stream.readInt();
    String tmpAIP = null;
    int tmpAPort = -1;
    if(s2sInnerLogic.mode == SLAVE_MODE){
      tmpAIP = stream.readUTF();
      tmpAPort = stream.readInt();

      ArbitratorIP = tmpAIP;
      ArbitratorPort = tmpAPort;
    }
    //------------------------------------------------------------------
    isCanBeSlave = tmpCanBeSlave==1?true:false;
    }finally{stream.close();}
   }

    /**
     * Получить уникальный идентификатор сервера
     * @return уникальный идентификатор сервера
     * @throws RemoteException разрыв соединения
    */
    public long getGUID() throws RemoteException
     {return s2sInnerLogic.GUIDServer;}

   /**
    * Удалить сервер из сети
    * @throws java.rmi.RemoteException разрыв соединения
    * @throws IOException конфигурационные файлы сети недоступны
    */
   public void remove() throws RemoteException, IOException{
    //Зануляем информацию о прошлом арбитре
    ArbitratorIP = "";
    ArbitratorPort = 1099;
    //Оповещаем о переходе работы сервера в автономный режим работы
    s2sInnerLogic.mode = AUTONOMIC_MODE;
    //Останавлимаем сервис пингования арбитра
    serviceArbitratorPing.stopPing();
    save();
    s2sInnerLogic.save();
    //Активировать обработчики события: Удаление сервера из сети арбитром
    activateActionListener(removeActionList);
    //Активировать обработчики события: Переход в автоматический режим работы
    NetworkLogic.ActivateEventAutomaticMode();
    //Сбросить флаг о том, что сервер находился в сети
    countInNet = 0;
   }

   /**
    * Добавить обработчик события: "Соединение с арбитром восстановлено"
    * @param listener обработчик события
    */
   public void addReconnectArbitratorListener(ActionListener listener){
     reconnectArbitratorActionList.add(listener);
   }

   /**
    * Добавить обработчик события: "Соединение с арбитром потеряно"
    * @param listener обработчик события
    */
   public void addFailArbitratorListener(ActionListener listener){
     failArbitratorActionList.add(listener);
   }

    /**
     * Добавить задачу в планировщик
     * @param task задача, не null !
     * @return true - задача добавлена, false - задача с указанным id уже есть
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public boolean addTask(JTask task) throws RemoteException{
        return taskManager.getLocalTM().addTask(task);
    }

    /**
     * Удалить задачу из планировщика
     * Процессы связанные с задачей уничтожаются
     * @param taskID идентификатор задачи
     * @return true - задача удалена, false - задачи нет
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public boolean removeTask(long taskID) throws RemoteException{
        return taskManager.getLocalTM().removeTask(taskID);
    }

    /**
     * Остановить выполнение задачи
     * @param taskID идентификатор задачи
     * @return true - задача остановлена, false - задачи нет
     * @throws java.rmi.RemoteException ошибка сети
     */
    public boolean stopTask(long taskID) throws RemoteException{
        return taskManager.getLocalTM().stopTask(taskID);
    }

    /**
     * Запросить количество задач в планировщике
     * @return число задач в планировщике
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public int getTaskCount() throws RemoteException{
        return taskManager.getLocalTM().getTaskCount();
    }

    /**
     * Проверить наличие задач в планировщике
     * @return true - здач нет, иначе false
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public boolean isEmpty() throws RemoteException{
        return taskManager.getLocalTM().isEmpty();
    }

    /**
     * Установить максимальное количество активных задач для планировщика
     * нижнего уровня
     * @param max максимально количество активных задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public void setMaxActiveCount(int max) throws RemoteException{
        taskManager.getLocalTM().setMaxActiveCount(max);
    }

    /**
     * Запросить максимальное количество активных задач планировщика нижнего
     * уровня
     * @return максимально количество активных задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public int getMaxActiveCount() throws RemoteException{
        return taskManager.getLocalTM().getMaxActiveCount();
    }

    /**
     * Проверить наличие задачи
     * @param taskID идентификатор задачи
     * @return true - задача присутствует, false иначе
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public boolean containsTask(long taskID) throws RemoteException{
        return taskManager.getLocalTM().containsTask(taskID);
    }

    /**
     * Запросить количество активных задач для планировщика
     * нижнего уровня
     * @return количество активных задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public int getActiveCount() throws RemoteException{
        return taskManager.getLocalTM().getActiveCount();
    }


    /**
     * Установка обработчика события "Переход сервера в режим работы ведомый"
     * @param listener - обработчик события
     */
    public void addActionModeSlaveServer(ActionListener listener){
      slaveActionList.push(listener);
    }

    /**
     * Установка обработчика события "Первое вступление ведомого сервера в сеть после запуска"
     * @param listener - обработчик события
    */
    public void addActionInitNetServer(ActionListener listener){
      firstConnectActionList.push(listener);
    }

    /**
     * Установка обработчика события "Удаление сервера из сети арбитром"
     * @param listener - обработчик события
    */
    public void addActionRemoveThisServerFromNetwork(ActionListener listener){
      removeActionList.push(listener);
    }

    /**
     * Запросить задачу
     * @param taskID идентификатор задачи
     * @return задача или null в случае отсутствия
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public JTask getTask(long taskID) throws RemoteException{
        return taskManager.getLocalTM().getTask(taskID);
    }

    /**
     * Запросить идентификаторы активных задач
     * @return массив идентификаторов задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public long[] getActiveTasks() throws RemoteException{
        return taskManager.getLocalTM().getActiveTasks();
    }

    /**
     * Запросить идентификаторы ожидающих и приостановленных задач
     * @return массив идентификаторов задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public long[] getWaitTasks() throws RemoteException{
        return taskManager.getLocalTM().getWaitTasks();
    }

    /**
     * Запросить идентификаторы задач в планировщике
     * @return массив идентификаторов задач
     * @throws java.rmi.RemoteException ошибка сети
     * @author Aleksey ID=2001
     */
    public long[] getAllTasks() throws RemoteException{
        return taskManager.getLocalTM().getAllTasks();
    }

}
